package com.qjs.smterp.web.beans;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.qjs.smterp.web.utils.FacesContextUtils;

/**
 * @param <I>
 *            - Item
 * @param <F>
 *            - Filter
 * 
 *            This abstract class is using for basic ManagedBean that doesn't
 *            need to be paginated on DB side.
 * 
 *            Here we should supply method getId for row access from map of
 *            rows.
 * 
 * @see AbstractManagedBeanSimple to work with very simple case of dataTable
 *      without sorting and pagination on DB side.
 */
public abstract class AbstractManagedBean<I, F> {
	protected final Logger logger = Logger.getLogger(getLoggerName());

	protected List<I> list = Collections.emptyList();
	protected boolean editMode;
	protected I selected;
	protected I item;
	protected F filter;

	protected abstract I[] searchCallback(F filter) throws Exception;

	protected abstract String addCallback(I item) throws Exception;

	protected abstract String modifyCallback(I item) throws Exception;

	protected abstract String deleteCallback(String id) throws Exception;

	protected abstract String getId(I item);

	protected String getLoggerName() {
		return "BASIC";
	}

	protected String getEditFormName() {
		return null;
	}

	@SuppressWarnings("unchecked")
	protected I newItem() {
		try {
			return (I) newInstance(ITEM_INDEX);
		} catch (Exception e) {
			FacesContextUtils.addErrorExceptionMessage(e);
			logger.error("", e);
			throw new RuntimeException("An error occures at newItem method", e);
		}
	}

	@SuppressWarnings("unchecked")
	protected F newFilter() {
		try {
			return (F) newInstance(FILTER_INDEX);
		} catch (Exception e) {
			FacesContextUtils.addErrorExceptionMessage(e);
			logger.error("", e);
			throw new RuntimeException("An error occures at newFilter method",
					e);
		}
	}

	public AbstractManagedBean() {
		setItem(null);
		setFilter(null);
	}

	public List<I> getList() {
		/*
		 * if (Collections.EMPTY_LIST == list &&
		 * FacesContext.getCurrentInstance().getRenderResponse()) { search(); }
		 */
		return list;
	}

	public String reset() {
		setFilter(null);
		return search();
	}

	public String search() {
		try {
			list = new ArrayList<I>(Arrays.asList(searchCallback(filter)));

			if (null == map) {
				map = new LinkedHashMap<String, I>(list.size());
			} else {
				map.clear();
			}
			for (I item : list) {
				map.put(getId(item), item);
			}
			setSelectedId(null);
		} catch (Exception e) {
			FacesContextUtils.addErrorExceptionMessage(e);
			logger.error("", e);
		}
		editMode = false;
		return null;
	}

	public String add() {
		setItem(null);
		editMode = Boolean.FALSE;

		return null;
	}

	public String modify() {
		setItem(getItem(selectedId));
		editMode = Boolean.TRUE;

		return null;
	}

	public String doAdd() {
		String result;
		try {
			result = addCallback(item);
			search();
		} catch (Exception e) {
			FacesContextUtils.addErrorExceptionMessage(e);
			logger.error("", e);
			result = null;
		}
		return result;
	}

	public String doModify() {
		String result;
		try {
			result = modifyCallback(item);
			search();
		} catch (Exception e) {
			FacesContextUtils.addErrorExceptionMessage(e);
			logger.error("", e);
			result = null;
		}
		return result;
	}

	public String doDelete() {
		String result;

		try {
			result = deleteCallback(selectedId);
			search();
		} catch (Exception e) {
			FacesContextUtils.addErrorExceptionMessage(e);
			logger.error("", e);
			result = null;
		}
		return result;
	}

	public void setSelectedId(String selectedId) {
		this.selectedId = selectedId;
		selected = getItem(selectedId);
	}

	public String getSelectedId() {
		return selectedId;
	}

	public I getSelected() {
		return selected;
	}

	public void setSelected(I selected) {
		this.selected = selected;
	}

	public I getItem() {
		return item;
	}

	public I getItem(String id) {
		if (id != null) {
			return map.get(id);
		}
		return null;
	}

	public void setItem(I item) {
		if (null == item) {
			this.item = newItem();
		} else {
			this.item = item;
		}
	}

	public F getFilter() {
		return filter;
	}

	public void setFilter(F filter) {
		if (null == filter) {
			this.filter = newFilter();
		} else {
			this.filter = filter;
		}
	}

	public Boolean getEditMode() {
		return editMode;
	}

	@SuppressWarnings("unchecked")
	private <E> E newInstance(int index) throws Exception {
		ParameterizedType type = (ParameterizedType) this.getClass()
				.getGenericSuperclass();
		Class<E> clazz = (Class<E>) type.getActualTypeArguments()[index];

		if (Number.class.isAssignableFrom(clazz)) {
			// do not need to initialize number objects by constructor
			return null;
		} else {
			return clazz.newInstance();
		}
	}

	private static final int ITEM_INDEX = 0;
	private static final int FILTER_INDEX = 1;

	private String selectedId;
	protected Map<String, I> map;
}